/**
 * @author W
 * @date 2020/4/5 11:47
 */

// 类型保护与类型区分
// 如果一个值的类型是 A | B，我们能够确定的是它包含了 A 和 B中共有的成员。 这个例子里， Bird具有一个 fly成员,Fish 没有
// 我们不能确定一个 Bird | Fish类型的变量是否有 fly方法。 如果变量在运行时是 Fish类型，那么调用 pet.fly()就出错了。

// 定义两个接口
interface Bird {
    fly:()=>string,
    name:string
}

interface Fish {
    eat:()=>void,
    name:string
}

{
    // 声明一个函数，参数可能是Bird类型或者Fish类型
    function f(animal:Bird|Fish):void {
        // 每一个成员访问都会报错，当我们想确切地了解是否为 Fish或 Bird类型时怎么办，我们可以使用类型断言
        // if (animal.fly){
        //     animal.fly()
        // } else {
        //     animal.eat()
        // }
        if ((animal as Bird).fly){
            (animal as Bird).fly()
        } else {
            (animal as Fish).eat()
        }
        // 这样虽然可以保证编译通过，但是可以注意到我们不得不多次使用类型断言。非常繁琐
    }
}

// 假若我们一旦检查过类型，就能在之后的每个分支里清楚地知道参数的类型的话就好了。就不用每次都写类型断言
// TypeScript里的 类型保护机制 让它成为了现实。 类型保护就是一些表达式，它们会在运行时检查以确保在 某个作用域 里的类型。
{
     // 要定义一个类型保护，我们只要简单地定义一个函数，它的返回值是一个 类型谓词：
    function isFish(type:Bird | Fish):type is Fish {
        return (type as Fish).eat !== undefined   // 使用类型断言判断这个类型是否具有eat属性，有就是Fish类型，否则就是Bird类型
    }
    // type is Fish 就是一个类型谓词，谓词为 parameterName is Type这种形式， parameterName必须是来自于当前函数签名里的一个参数名。

    function f1(animal:Bird | Fish):void {
        if (isFish(animal)){ // 每当使用一些变量调用 isFish时，TypeScript会将变量缩减为那个具体的类型
            animal.eat()
        } else {  // 注意TypeScript不仅知道在 if分支里 animal 是 Fish类型； 它还清楚在 else分支里，一定 不是 Fish类型，一定是 Bird类型。编辑器还会给出对应类型的代码提示，非常友好
            animal.fly()
        }
    }

    let fish:Fish = {
        name:'xiaoYu',
        eat:()=>{
            console.log('fish eating....')
        }
    }

    f1(fish) // fish eating....
}

// 使用 typeof类型保护
// typeof 只能检测原始类型，对于复杂类型不适用， typeof类型保护只有两种形式能被识别： typeof v === "typename"和 typeof v !== "typename"，
// 并且 "typename"必须是 "number"， "string"， "boolean"或 "symbol"，其中之一 。但是TypeScript并不会阻止你与其它字符串比较，语言不会把那些表达式识别为类型保护。
{
    function f1(param:number | string):void {
        if (typeof param === 'number'){
            console.log(++param)
        } else {
            console.log(param.length)
        }
    }

    f1(20) // 21
    f1('wang.mao') // 8
}

// 使用instanceof 类型保护
// instanceof类型保护是通过构造函数来细化类型的一种方式，instanceof的右侧要求是一个构造函数
{
    // 定义一个接口
    interface Space {
        getSpace:(a?:string) => void
    }

    // 通过参数数字 返回有多少数量的空格字符串
    class SpaceOfNumber implements Space {
        constructor(private param:number) {}
        getSpace(){
            console.warn(new Array(this.param+1).join(' '))
        }
    }

    // 通过参数字符串 返回有字符串长度数量的空格字符串
    class SpaceOfString implements Space {
        constructor(private params:string) {}
        getSpace(a:string){
            console.warn(a,new Array(this.params.length+1).join(' '))
        }
    }

    // 通过随机概率返回实例
    let space = Math.random() > 0.5 ? new SpaceOfNumber(5) : new SpaceOfString('wang.mao')
    if (space instanceof SpaceOfString) { //  类型细化为 'SpaceOfString'
        space.getSpace('Hello!')   //类型为 SpaceOfNumber 可以传入a参数
    } else {
        space.getSpace() //  类型细化为 'SpaceOfNumber' 不用传入a参数
    }
}
// 四大类型保护机制
/*
* 1. 类型断言  param as Type
* 2. 类型谓词  param is Type
* 3. typeof 局限于原始类型
* 4. instanceof  右侧要求是一个构造函数
*/
